{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "dddbe576",
   "metadata": {},
   "source": [
    "# 量子态编码经典数据\n",
    "\n",
    "<em> Copyright (c) 2021 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. </em>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "9ddf4180",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<style>pre { white-space: pre !important; }</style>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from IPython.core.display import HTML\n",
    "display(HTML(\"<style>pre { white-space: pre !important; }</style>\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "93be1348",
   "metadata": {},
   "source": [
    "## 概览\n",
    "\n",
    "量子编码是一个将经典信息转化为量子态的过程。在使用量子算法解决经典问题的过程中，量子编码是非常重要的一步。比如在[量子分类器](./QClassifier_CN.ipynb)中，第一步就是将经典信息转变为可以传入量子电路的量子态。大多数量子编码的方法都可以看作是作用在 $\\left| 0^n \\right>$ 态上的参数化电路，并且参数化电路中的参数是由经典信息决定。\n",
    "\n",
    "本教程中我们将讨论五种量子编码的方式，包括**基态编码** [1]、**振幅编码** [1]、**角度编码** [1]、**IQP 编码** [2]和**哈密顿量演化编码** [3]。在量桨中，我们内置了前四种量子编码方式。\n",
    "\n",
    "## 基态编码\n",
    "\n",
    "基态编码（basis encoding）是最直观的一种编码方式。它把一个长度为 $n$ 的二进制字符串 $x$ 转化为一个有 $n$ 个量子比特的量子态 $\\left|x\\right> = \\left|i_x\\right>$。其中，$\\left|i_x\\right>$ 是一个计算基态。比如说，当经典数据为 $x=1011$ 时，对应得到的量子态就是 $\\left|1011\\right>$。下面，我们来看看如何使用量桨实现基态编码："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "44bd5d8e",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 导入所需要的包\n",
    "import paddle\n",
    "from paddle_quantum.ansatz import Circuit\n",
    "from paddle_quantum.gate import BasisEncoding, AmplitudeEncoding, AngleEncoding, IQPEncoding\n",
    "import paddle_quantum as pq\n",
    "import numpy as np"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "59ef733b",
   "metadata": {},
   "source": [
    "从 $\\left| 0^n \\right>$ 开始，如果第 $i$ 位的经典信息是1，那么我们在对应的第 $i$ 个量子比特上作用一个 $X$ 门："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "eee7f90d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "--X--\n",
      "     \n",
      "-----\n",
      "     \n",
      "--X--\n",
      "     \n",
      "--X--\n",
      "     \n"
     ]
    }
   ],
   "source": [
    "# 量子比特的数量等于经典信息的长度\n",
    "n = 4\n",
    "# 初始化电路\n",
    "basis_enc = Circuit(n)\n",
    "# x 是经典信息\n",
    "x = '1011'\n",
    "# 如果第 i 维经典信息是1，那么我们在对应的量子比特上作用一个 X 门\n",
    "for i in range(len(x)):\n",
    "    if x[i] == '1':\n",
    "        basis_enc.x(i)\n",
    "\n",
    "print(basis_enc)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4e1525b7",
   "metadata": {},
   "source": [
    "可以看到经过基态编码后的量子态为："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "6293b67b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j\n",
      " 0.+0.j 1.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j]\n"
     ]
    }
   ],
   "source": [
    "init_state = pq.state.zero_state(n)\n",
    "basis_quantum_state = basis_enc(init_state)\n",
    "\n",
    "print(basis_quantum_state)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8703d88f",
   "metadata": {},
   "source": [
    "这就是我们想要得到的 $\\left|1011\\right>$ 态。\n",
    "\n",
    "在量桨中，我们提供内置的基态编码函数："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "3f58d5a3",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j\n",
      " 0.+0.j 1.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j]\n"
     ]
    }
   ],
   "source": [
    "# 内置的基态编码函数\n",
    "built_in_basis_enc = BasisEncoding(num_qubits=n)\n",
    "# 经典信息 x 需要是 Tensor 的形式\n",
    "x = paddle.to_tensor([1, 0, 1, 1])\n",
    "built_in_basis_enc_state = built_in_basis_enc(feature=x)\n",
    "\n",
    "print(built_in_basis_enc_state)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "30f059ba",
   "metadata": {},
   "source": [
    "## 振幅编码\n",
    "\n",
    "振幅编码（amplitude encoding）将一个 $N$ 维的经典向量 $\\mathbf{x}$ 编码到一个有 $n$ 个量子比特的量子态，其中 $n = \\lceil\\log_2(N)\\rceil$，且\n",
    "\n",
    "$$\n",
    "\\begin{align*} \\left|\\mathbf{x}\\right> = \\sum\\limits_{i}^{N}x_i\\left|i\\right>\\end{align*},\n",
    "$$\n",
    "\n",
    "这里，$\\left\\{\\left|i\\right>\\right\\}$ 是希尔伯特空间的一组计算基。因为经典信息构成了一个量子态的振幅，所以这个经典信息需要满足归一化条件：$\\left|\\mathbf{x}\\right|^{2} = 1$。  \n",
    "比如说，当 $\\mathbf{x} = \\begin{bmatrix} \\frac{1}{2}\\\\ \\frac{1}{2}\\\\ -\\frac{1}{2}\\\\ -\\frac{1}{2} \\end{bmatrix}$ 时，编码后得到的量子态就是 $\\left|\\mathbf{x}\\right> = \\frac{1}{2}\\left|00\\right> + \\frac{1}{2} \\left|01\\right> - \\frac{1}{2} \\left|10\\right> - \\frac{1}{2} \\left|11\\right>$。  \n",
    "我们也举一个当 $N$ 小于 $2^{n}$ 时的例子，如果 $\\mathbf{y} = \\begin{bmatrix} \\frac{1}{\\sqrt{3}}\\\\\\frac{1}{\\sqrt{3}}\\\\\\frac{1}{\\sqrt{3}} \\end{bmatrix}$， 那么对应的量子态就是$\\left|\\mathbf{y}\\right> = \\frac{1}{\\sqrt{3}}\\left|00\\right> + \\frac{1}{\\sqrt{3}}\\left|01\\right> + \\frac{1}{\\sqrt{3}}\\left|10\\right>$。\n",
    "\n",
    "显然，振幅编码无法简单地被表示为电路的形式。实际上，振幅编码的实现需要用到一种叫做任意态制备的方法 [1]。但不用担心，在量桨中，我们提供了内置的振幅编码函数可以直接使用："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "2436b99f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0.57735026+0.j 0.57735026+0.j 0.57735026+0.j 0.        +0.j]\n"
     ]
    }
   ],
   "source": [
    "# 内置振幅编码函数\n",
    "# 量子比特的数目为2\n",
    "n = 2\n",
    "# 初始化电路\n",
    "built_in_amplitude_enc = AmplitudeEncoding(num_qubits=n)\n",
    "# 经典信息 x 需要是 Tensor 的形式\n",
    "x = paddle.to_tensor([0.5, 0.5, 0.5])\n",
    "state = built_in_amplitude_enc(x)\n",
    "\n",
    "print(state)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "14fc67cd",
   "metadata": {},
   "source": [
    "在量桨中，我们会默认归一化输入的经典向量。可以看到，编码后得到的量子态就是 $\\frac{1}{\\sqrt{3}}\\left|00\\right> + \\frac{1}{\\sqrt{3}}\\left|01\\right> + \\frac{1}{\\sqrt{3}}\\left|10\\right>$。\n",
    "\n",
    "## 角度编码\n",
    "\n",
    "角度编码（angle encoding）运用了旋转门来编码经典信息 $\\mathbf{x}$，这些旋转门的旋转角度由经典信息决定，\n",
    "\n",
    "$$\n",
    "\\left|\\mathbf{x}\\right> = \\bigotimes_{i}^{n} R(\\mathbf{x}_i) \\left| 0^n \\right>,\n",
    "$$\n",
    "\n",
    "在这里可以使用 $R_x$、$R_y$ 和 $R_z$ 中的任意一种来作为 $R$。通常情况下，量子比特的数量是等于经典信息的维度的。\n",
    "比如说，当 $\\mathbf{x} = \\begin{bmatrix} \\pi \\\\ \\pi\\\\ \\pi \\end{bmatrix}$ 时，如果我们使用 $R_y$，那么角度编码就会使每个量子比特绕 $y$ 轴旋转$180$度，对应的量子态就会是 $\\left|111\\right>$。\n",
    "\n",
    "我们可以通过如下方法构造角度编码的电路："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "bbfd0d21",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "--Ry(3.142)--\n",
      "             \n",
      "--Ry(3.142)--\n",
      "             \n",
      "--Ry(3.142)--\n",
      "             \n"
     ]
    }
   ],
   "source": [
    "# 量子比特的数量等于经典信息的维度\n",
    "n = 3\n",
    "# 初始化电路\n",
    "angle_enc = Circuit(n)\n",
    "# x 是需要编码的经典信息\n",
    "x = paddle.to_tensor([np.pi, np.pi, np.pi], 'float64')\n",
    "# 加一层 Ry 旋转门\n",
    "for i in range(len(x)):\n",
    "    angle_enc.ry(qubits_idx=i, param=x[i])\n",
    "        \n",
    "print(angle_enc)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d062e164",
   "metadata": {},
   "source": [
    "对应编码后的量子态就是："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "5905b719",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[(-0+0j), 0j, 0j, (-0+0j), 0j, (-0+0j), (-0+0j), (1+0j)]\n"
     ]
    }
   ],
   "source": [
    "init_state = pq.state.zero_state(n)\n",
    "angle_quan_state = angle_enc(init_state)\n",
    "\n",
    "print([np.round(i, 2) for i in angle_quan_state.data.numpy()])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "33a722a2",
   "metadata": {},
   "source": [
    "正是我们想要的 $\\left|111\\right>$。\n",
    "\n",
    "在量桨中，我们也提供了内置的角度编码的函数："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "7462102b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[(-0+0j), 0j, 0j, (-0+0j), 0j, (-0+0j), (-0+0j), (1+0j)]\n"
     ]
    }
   ],
   "source": [
    "# 内置角度编码函数\n",
    "# 量子比特的数\n",
    "n = 3\n",
    "# 初始化电路\n",
    "built_in_angle_enc = AngleEncoding(num_qubits=n, encoding_gate=\"ry\", feature=x)\n",
    "# x是需要编码的经典信息\n",
    "x = paddle.to_tensor([np.pi, np.pi, np.pi], 'float64')\n",
    "init_state = pq.state.zero_state(n)\n",
    "state = built_in_angle_enc(state=init_state)\n",
    "\n",
    "print([np.round(i, 2) for i in state.data.numpy()])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c11672af",
   "metadata": {},
   "source": [
    "## IQP 编码\n",
    "\n",
    "IQP 编码（instantaneous quantum polynomial style encoding）是一个相对来说比较复杂的编码方式。我们把一个经典信息 $\\mathbf{x}$ 编码到\n",
    "\n",
    "$$\n",
    "\\left|\\mathbf{x}\\right> = \\left(\\mathrm{U}_\\mathrm{Z}(\\mathbf{x})\\mathrm{H}^{\\otimes n}\\right)^{r}\\left|0^n\\right>,\n",
    "$$\n",
    "\n",
    "在这里，$r$ 表示电路的深度，也就是 $\\mathrm{U}_\\mathrm{Z}(\\mathbf{x})\\mathrm{H}^{\\otimes n}$ 重复的次数。$\\mathrm{H}^{\\otimes n}$ 是一层作用在所有量子比特上的Hadamard门。$\\mathrm{U}_\\mathrm{Z}(\\mathbf{x})$ 则是 IQP 编码中最重要的一步：\n",
    "\n",
    "$$\n",
    "\\mathrm{U}_\\mathrm{Z}(\\mathbf{x})=\\prod\\limits_{[i,j]\\in S}R_{Z_iZ_j}(x_ix_j)\\bigotimes_{k=1}^{n} R_z(x_k),\n",
    "$$\n",
    "\n",
    "这里的 $S$ 是一个集合，对于这个集合中的每一对量子比特，我们都需要对它们作用 $R_{ZZ}$ 门。\n",
    "\n",
    "首先我们考虑一个简单的两量子比特门：$R_{Z_1Z_2}(\\theta)$。它的数学表达式 $e^{-i\\frac{\\theta}{2}Z_1\\otimes Z_2}$ 可以看做是绕 ZZ 旋转的两比特旋转门，并且使得这两个量子比特纠缠。我们可以使用如下方法在量桨中实现这个门："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "4ed6549a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "--*-----------------*--\n",
      "  |                 |  \n",
      "--x----Rz(6.000)----x--\n",
      "                       \n"
     ]
    }
   ],
   "source": [
    "# 量子比特的数量\n",
    "n = 2\n",
    "# 初始化电路\n",
    "Rzz = Circuit(n)\n",
    "# x 是经典信息\n",
    "x = paddle.to_tensor([2, 3], 'float64')\n",
    "# 实现 RZZ 门\n",
    "Rzz.cnot(qubits_idx=[0, 1])\n",
    "Rzz.rz(qubits_idx=1, param=x[0]*x[1])\n",
    "Rzz.cnot(qubits_idx=[0, 1])\n",
    "        \n",
    "print(Rzz)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cafaed9d",
   "metadata": {},
   "source": [
    "在 $\\mathrm{U}_\\mathrm{Z}$中，$R_{ZZ}$ 会作用在每一对属于集合 $S$ 的量子比特对上。在量桨内置的 IQP 编码函数中，用户可以自定义这个集合 $S$。\n",
    "\n",
    "现在我们来看看如何使用量桨实现这个电路："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "05abe637",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "--H----Rz(-1.45)----*-----------------*------------------------------------------------\n",
      "                    |                 |                                                \n",
      "--H----Rz(3.000)----x----Rz(-4.35)----x----*-----------------*-------------------------\n",
      "                                           |                 |                         \n",
      "--H----Rz(2.000)---------------------------x----Rz(6.000)----x----*-----------------*--\n",
      "                                                                  |                 |  \n",
      "--H----Rz(-0.05)--------------------------------------------------x----Rz(-0.10)----x--\n",
      "                                                                                       \n"
     ]
    }
   ],
   "source": [
    "# 量子比特的数量\n",
    "n = 4\n",
    "# 初始化电路\n",
    "iqp_enc = Circuit(n)\n",
    "# x 是经典信息\n",
    "x = paddle.to_tensor([-1.45, 3, 2, -0.05], 'float64')\n",
    "# S 集合中的每一对量子比特对都要加上 RZZ 门\n",
    "S = [[0, 1], [1, 2], [2, 3]]\n",
    "# r 是 U 重复的次数\n",
    "r = 1\n",
    "\n",
    "for i in range(r):\n",
    "    # 加上一层 Hadamard 门\n",
    "    iqp_enc.h(\"full\")\n",
    "    # 加上一层旋转门 Rz\n",
    "    iqp_enc.rz(qubits_idx=\"full\",param=x)\n",
    "    # 加上 RZZ 门\n",
    "    for k in S:\n",
    "        iqp_enc.cnot(k)\n",
    "        iqp_enc.rz(qubits_idx=k[1], param=x[k[0]]*x[k[1]])\n",
    "        iqp_enc.cnot(k)\n",
    "            \n",
    "print(iqp_enc)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ef8bffcb",
   "metadata": {},
   "source": [
    "编码后的量子态是："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "0c84ae1f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[(-0.20396-0.14456j), (-0.22328-0.11246j), (0.15379-0.1971j), (0.16345-0.18916j), (-0.13157+0.21258j), (-0.09832+0.22985j), (-0.09832-0.22985j), (-0.08671-0.23448j), (-0.11345-0.22278j), (-0.14547-0.20332j), (0.22776-0.10308j), (0.23263-0.09157j), (0.07689-0.23788j), (0.04047-0.2467j), (0.15046+0.19966j), (0.14029+0.20693j)]\n"
     ]
    }
   ],
   "source": [
    "init_state = pq.state.zero_state(n)\n",
    "iqp_quantum_state = iqp_enc(init_state)\n",
    "\n",
    "print([np.round(i, 5) for i in iqp_quantum_state.data.numpy()])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1ab76781",
   "metadata": {},
   "source": [
    "在量桨中，我们提供了内置的 IQP 编码函数。需要注意的是，我们上述描述的这种 IQP 编码仅仅是 IQP 编码方式这个大类中的一个特例。在更为广义的 IQP 编码方式中，你可以把 $R_{Z}$ 替换为 $R_{Y}$ 或者 $R_{X}$，还可以把 $R_{ZZ}$ 替换为 $R_{XX}$ 或者 $R_{YY}$。除此之外，你甚至可以考虑三量子比特旋转门，并在 $\\mathrm{U}(\\mathbf{x})$ 的后面加上多层三量子比特旋转门。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "b941cb2e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[(-0.20396-0.14456j), (-0.22328-0.11246j), (0.15379-0.1971j), (0.16345-0.18916j), (-0.13157+0.21258j), (-0.09832+0.22985j), (-0.09832-0.22985j), (-0.08671-0.23448j), (-0.11345-0.22278j), (-0.14547-0.20332j), (0.22776-0.10308j), (0.23263-0.09157j), (0.07689-0.23788j), (0.04047-0.2467j), (0.15046+0.19966j), (0.14029+0.20693j)]\n"
     ]
    }
   ],
   "source": [
    "# 内置 IQP 编码\n",
    "# 量子比特的数量\n",
    "n = 4\n",
    "# 初始化电路\n",
    "# r 是 U 重复的次数\n",
    "r = 1\n",
    "# S 集合中的每一对量子比特对都要加上 RZZ 门\n",
    "S = [[0, 1], [1, 2], [2, 3]]\n",
    "built_in_iqp_enc = IQPEncoding(qubits_idx=S, num_qubits = n, num_repeat=r, feature=x)\n",
    "# x 是经典信息\n",
    "x = paddle.to_tensor([-1.45, 3, 2, -0.05], 'float64')\n",
    "init_state = pq.state.zero_state(n)\n",
    "built_in_iqp_enc_state = built_in_iqp_enc(state=init_state)\n",
    "\n",
    "print([np.round(i, 5) for i in built_in_iqp_enc_state.data.numpy()])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9d5f4d71",
   "metadata": {},
   "source": [
    "## 哈密顿量演化编码\n",
    "\n",
    "哈密顿量演化编码（Hamiltonian evolution ansatz encoding）运用了特罗特公式（Trotter formula）来近似一个演化，获取哈伯特模型（Hubbard model）的基态能量就使用了这种编码方式 [4]。\n",
    "\n",
    "$$\n",
    "\\left|\\mathbf{x}\\right> = \\left(\\prod\\limits_{i=1}^{n}R_{Z_iZ_{i+1}}(\\frac{t}{T}x_{i})R_{Y_iY_{i+1}}(\\frac{t}{T}x_{i})R_{X_iX_{i+1}}(\\frac{t}{T}x_{i})\\right)^{T}\\bigotimes_{i=1}^{n+1}\\left|\\psi_{i}\\right>,\n",
    "$$\n",
    "\n",
    "这里的 $R_{XX}$、$R_{YY}$ 和 $R_{ZZ}$ 都是 IQP 编码方式中提到的两量子比特旋转门，$T$ 是进行特罗特步骤的次数，$\\left|\\psi_{i}\\right>$ 则是一个哈尔随机（Haar-random）单比特量子态。\n",
    "在实际实现的过程中，你可以先准备哈尔随机量子态，然后再加上 $T$ 层 $R_{XX}$、$R_{YY}$、$R_{ZZ}$ 门。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "585a2e86",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "## 参考文献\n",
    "\n",
    "[1] Schuld, Maria. \"Quantum machine learning models are kernel methods.\" [arXiv:2101.11020 (2021).](https://arxiv.org/abs/2101.11020)\n",
    "\n",
    "[2] Havlíček, Vojtěch, et al. \"Supervised learning with quantum-enhanced feature spaces.\" [Nature 567.7747 (2019): 209-212.](https://www.nature.com/articles/s41586-019-0980-2)\n",
    "\n",
    "[3] Huang, Hsin-Yuan, et al. \"Power of data in quantum machine learning.\" [Nature Communications 12.1 (2021): 1-9.](https://www.nature.com/articles/s41467-021-22539-9)\n",
    "\n",
    "[4] Cade, Chris, et al. \"Strategies for solving the Fermi-Hubbard model on near-term quantum computers.\" [Physical Review B 102.23 (2020): 235122.](https://journals.aps.org/prb/abstract/10.1103/PhysRevB.102.235122)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
